package com.voicecomm.recognition;

import com.voicecomm.database.CommunicationLog;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 通联语音文本实体抽取器
 * 从识别文本中抽取呼号、QTH、设备型号、信号报告、天线与高度等信息
 */
public class CommEntityExtractor {

    // 简宽呼号匹配（通用兜底）
    private static final Pattern CALLSIGN_PATTERN = Pattern.compile("\\b([A-Z]{1,2}[0-9][A-Z0-9]{2,4})\\b", Pattern.CASE_INSENSITIVE);

    // 中国业余常见前缀优先
    private static final Pattern CN_CALLSIGN_PATTERN = Pattern.compile("\\b((?:BA|BD|BG|BH|BI)[0-9][A-Z]{2,4})\\b", Pattern.CASE_INSENSITIVE);

    // RST 信号报告（可包含语音常见说法，如59、579、599）
    private static final Pattern RST_PATTERN = Pattern.compile("\\b(59|579|599|[1-5][1-9][1-9])\\b");

    // QTH/地址关键词
    private static final Pattern QTH_PATTERN = Pattern.compile("(?:(?:QTH|地址|位置|坐标|在)[:：\u3001]?)([\\u4e00-\\u9fa5A-Za-z0-9\\-\\s]{2,20})");

    // 设备型号关键词（机型/电台/设备/型号）
    private static final Pattern DEVICE_PATTERN = Pattern.compile("(?:(?:设备|电台|主机|型号|机型)[:：\u3001]?)([A-Za-z0-9\\-]{2,20})");

    // 天线关键词
    private static final Pattern ANTENNA_PATTERN = Pattern.compile("(?:(?:天线|天綫|天綫类型|天线类型)[:：\u3001]?)([\\u4e00-\\u9fa5A-Za-z0-9\\-\\s]{2,30})");

    // 高度（米）
    private static final Pattern HEIGHT_PATTERN = Pattern.compile("(?:(?:高度|海拔|离地)[:：\u3001]?)([0-9]{1,4})(?:\\s*(?:米|m|M))?");

    /**
     * 从文本中抽取信息并回填到日志对象（仅当目标字段为空时才写入，避免覆盖用户后续更准确的值）
     * @param text 识别文本
     * @param myCallsign 我的固定呼号（用于区分对端呼号）
     * @param log 当前通联日志
     */
    public void extractAndMerge(String text, String myCallsign, CommunicationLog log) {
        if (text == null || text.isBlank() || log == null) {
            return;
        }

        String normalized = text.trim();

        // 呼号：找到评分最高且非我的呼号，作为对端呼号
        findBestOtherCallsign(normalized, myCallsign).ifPresent(cs -> {
            if (log.getCallerId() == null || log.getCallerId().isBlank()) {
                log.setCallerId(myCallsign);
            }
            if (log.getReceiverId() == null || log.getReceiverId().isBlank() || "UNKNOWN".equalsIgnoreCase(log.getReceiverId())) {
                log.setReceiverId(cs.toUpperCase(Locale.ROOT));
            }
        });

        // RST
        if (isEmpty(log.getSignalReport())) {
            Matcher m = RST_PATTERN.matcher(normalized);
            if (m.find()) {
                log.setSignalReport(m.group(1));
            }
        }

        // QTH
        if (isEmpty(log.getQth())) {
            Matcher m = QTH_PATTERN.matcher(normalized);
            if (m.find()) {
                log.setQth(clean(m.group(1)));
            }
        }

        // 设备
        if (isEmpty(log.getDeviceModel())) {
            Matcher m = DEVICE_PATTERN.matcher(normalized);
            if (m.find()) {
                log.setDeviceModel(m.group(1).toUpperCase(Locale.ROOT));
            }
        }

        // 天线
        if (isEmpty(log.getAntennaType())) {
            Matcher m = ANTENNA_PATTERN.matcher(normalized);
            if (m.find()) {
                log.setAntennaType(clean(m.group(1)));
            }
        }

        // 高度
        if (log.getAntennaHeightMeters() == null) {
            Matcher m = HEIGHT_PATTERN.matcher(normalized);
            if (m.find()) {
                try {
                    log.setAntennaHeightMeters(Double.parseDouble(m.group(1)));
                } catch (NumberFormatException ignored) {}
            }
        }
    }

    private Optional<String> findBestOtherCallsign(String raw, String myCallsign) {
        String upper = raw.toUpperCase(Locale.ROOT);
        String myCs = myCallsign != null ? myCallsign.toUpperCase(Locale.ROOT) : "";

        // 1) 直接优先匹配中国呼号模式
        List<String> candidates = new ArrayList<>();
        Matcher mCN = CN_CALLSIGN_PATTERN.matcher(upper);
        while (mCN.find()) candidates.add(mCN.group(1));

        // 2) NATO/拼读映射后再次匹配
        String natoCollapsed = collapseSpelledLetters(upper);
        Matcher mCN2 = CN_CALLSIGN_PATTERN.matcher(natoCollapsed);
        while (mCN2.find()) candidates.add(mCN2.group(1));

        // 3) 通用模式兜底
        Matcher mAny = CALLSIGN_PATTERN.matcher(natoCollapsed);
        while (mAny.find()) candidates.add(mAny.group(1));

        // 4) 合并分散字母数字（如 "B D 1 B W V"）
        candidates.addAll(collectCollapsedTokenSequences(natoCollapsed));

        if (candidates.isEmpty()) return Optional.empty();

        // 评分与选择
        String best = null;
        int bestScore = Integer.MIN_VALUE;
        for (String cand : candidates) {
            String normalized = normalizeHomoglyphs(cand);
            if (normalized.equals(myCs)) continue;
            int score = scoreCallsign(normalized);
            if (score > bestScore) { bestScore = score; best = normalized; }
        }
        return Optional.ofNullable(best);
    }

    private static boolean isEmpty(String s) { return s == null || s.trim().isEmpty(); }

    private static String clean(String s) {
        if (s == null) return null;
        return s.replaceAll("[，。,.]$", "").trim();
    }

    // ==== Callsign 提升相关工具 ====
    private static final Map<String, String> NATO_MAP = buildNatoMap();
    private static final Set<String> KNOWN_PREFIX = new HashSet<>(Arrays.asList("BA","BD","BG","BH","BI"));

    private static Map<String, String> buildNatoMap() {
        Map<String, String> map = new HashMap<>();
        // NATO 拼读
        map.put("ALFA", "A"); map.put("ALPHA", "A");
        map.put("BRAVO", "B");
        map.put("CHARLIE", "C");
        map.put("DELTA", "D");
        map.put("ECHO", "E");
        map.put("FOXTROT", "F");
        map.put("GOLF", "G");
        map.put("HOTEL", "H");
        map.put("INDIA", "I");
        map.put("JULIET", "J"); map.put("JULIETT", "J");
        map.put("KILO", "K");
        map.put("LIMA", "L");
        map.put("MIKE", "M");
        map.put("NOVEMBER", "N");
        map.put("OSCAR", "O");
        map.put("PAPA", "P");
        map.put("QUEBEC", "Q");
        map.put("ROMEO", "R");
        map.put("SIERRA", "S");
        map.put("TANGO", "T");
        map.put("UNIFORM", "U");
        map.put("VICTOR", "V");
        map.put("WHISKEY", "W");
        map.put("XRAY", "X"); map.put("X-RAY", "X");
        map.put("YANKEE", "Y");
        map.put("ZULU", "Z");
        // 数字口语
        map.put("ZERO", "0"); map.put("NIL", "0");
        map.put("ONE", "1"); map.put("TWO", "2"); map.put("THREE", "3"); map.put("FOUR", "4");
        map.put("FIVE", "5"); map.put("SIX", "6"); map.put("SEVEN", "7"); map.put("EIGHT", "8"); map.put("NINE", "9");
        return map;
    }

    private static String collapseSpelledLetters(String textUpper) {
        // 将 NATO/数字词映射为单个字符；h
        String replaced = textUpper;
        for (Map.Entry<String, String> e : NATO_MAP.entrySet()) {
            replaced = replaced.replaceAll("\\b" + Pattern.quote(e.getKey()) + "\\b", e.getValue());
        }
        // 返回替换后的文本；分散字符的合并由 collectCollapsedTokenSequences 负责生成候选
        return replaced;
    }

    private static String normalizeHomoglyphs(String s) {
        // 常见同形纠正：O->0, I->1, S->5, Z->2, T->7 在数字位可能出现，但这里保守纠正更利于中国前缀
        String u = s.toUpperCase(Locale.ROOT);
        // 将可能被识别为数字的字母进行反向修正：0->O 在首前缀处，1->I 在后缀字母处
        if (u.length() >= 2 && Character.isDigit(u.charAt(1))) {
            // 前缀区更像字母
            u = u.replaceFirst("^B0", "BO");
        }
        // 常见误识别：8<->B, 0<->O, 1<->I, 5<->S
        u = u.replaceAll("^B8", "BB");
        return u;
    }

    private static int scoreCallsign(String cs) {
        int score = 0;
        String u = cs.toUpperCase(Locale.ROOT);
        // 长度偏好 4-6
        int len = u.length();
        if (len >= 4 && len <= 6) score += 5; else if (len <= 7) score += 2; else score -= 2;
        // 必须含一个数字
        if (u.matches(".*[0-9].*")) score += 3; else score -= 5;
        // 中国前缀加权
        for (String p : KNOWN_PREFIX) {
            if (u.startsWith(p)) { score += 6; break; }
        }
        // 仅一个数字更符合常见呼号
        int digits = u.replaceAll("[^0-9]", "").length();
        if (digits == 1) score += 3; else if (digits > 2) score -= 2;
        // 仅字母数字
        if (u.matches("[A-Z0-9]+")) score += 1; else score -= 1;
        return score;
    }

    private static List<String> collectCollapsedTokenSequences(String textUpper) {
        // 捕捉像 "B D 1 B W V" 或 "BD 1 BW V"，将相邻独立 token 合并
        List<String> outs = new ArrayList<>();
        String[] tokens = textUpper.split("\\s+");
        StringBuilder sb = new StringBuilder();
        for (String t : tokens) {
            String tt = t.replaceAll("[^A-Z0-9]", "");
            if (tt.length() == 1) {
                sb.append(tt);
            } else {
                if (sb.length() >= 3) {
                    outs.add(sb.toString());
                }
                sb.setLength(0);
                if (tt.length() <= 7) outs.add(tt);
            }
        }
        if (sb.length() >= 3) outs.add(sb.toString());
        // 过滤与规则不符的字符串
        List<String> filtered = new ArrayList<>();
        for (String c : outs) {
            if (c.matches("[A-Z]{1,2}[0-9][A-Z0-9]{2,4}")) filtered.add(c);
        }
        return filtered;
    }
}


